using System;
using System.Drawing;
using System.Xml;
using Wilco.SyntaxHighlighting.Engine.Node;
using Wilco.SyntaxHighlighting.Engine.Node.Implementation.Word;
using Wilco.SyntaxHighlighting.Engine.Node.Occurrence;
using Wilco.SyntaxHighlighting.Engine.Scanner;
using Wilco.SyntaxHighlighting.Engine.Tokenizer;

namespace Wilco.SyntaxHighlighting.Engine.Scanner.Implementation
{
    /// <summary>
    /// Represents a word scanner.
    /// </summary>
    public class WordScanner : ScannerBase
    {
        private WordNode[] wordNodes;
        private string lastToken;

        /// <summary>
        /// Gets or sets the word nodes.
        /// </summary>
        public WordNode[] WordNodes
        {
            get
            {
                return wordNodes;
            }
            set
            {
                if (value != wordNodes)
                {
                    wordNodes = value;
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="WordScanner"/> class.
        /// </summary>
        public WordScanner() : this(null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="WordScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        public WordScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult) : this(tokenizer, scannerResult, new WordNode[0])
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="WordScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <param name="wordNodes">An array of <see cref="WordNode"/> objects.</param>
        public WordScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult, WordNode[] wordNodes) : base(tokenizer, scannerResult)
        {
            this.wordNodes = wordNodes;
            SetID("WordScanner");
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new WordScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XmlElement element = (XmlElement)state;
			
            XmlNodeList wordGroupRootList = element["wordGroups"].ChildNodes;
            wordNodes = new WordNode[wordGroupRootList.Count];
            for (int i = 0; i < wordGroupRootList.Count; i++)
            {
                // Load settings.
                wordNodes[i] = new WordNode();
                FontConverter converter = new FontConverter();
                wordNodes[i].BackColor = ColorTranslator.FromHtml(wordGroupRootList[i].SelectSingleNode("settings/setting[@name='BackColor']").InnerText);
                wordNodes[i].ForeColor = ColorTranslator.FromHtml(wordGroupRootList[i].SelectSingleNode("settings/setting[@name='ForeColor']").InnerText);
                wordNodes[i].Font = (Font)converter.ConvertFromString(wordGroupRootList[i].SelectSingleNode("settings/setting[@name='Font']").InnerText);
                wordNodes[i].NavigateUrl = wordGroupRootList[i].SelectSingleNode("settings/setting[@name='NavigateUrl']").InnerText;
                wordNodes[i].IgnoreCase = Convert.ToBoolean(wordGroupRootList[i].SelectSingleNode("settings/setting[@name='IgnoreCase']").InnerText);

                // Load entities.
                foreach (XmlElement entityElement in wordGroupRootList[i]["entities"].ChildNodes)
                {
                    wordNodes[i].Entities.Add(entityElement.InnerText);
                }
            }
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            XmlDocument document = (XmlDocument)container;
            XmlElement element = (XmlElement)base.SaveState(container);
			
            FontConverter converter = new FontConverter();
            XmlElement wordRootElement = document.CreateElement("wordGroups");
            element.AppendChild(wordRootElement);
            XmlElement wordElement;
            XmlElement settingRootElement;
            XmlElement entityRootElement;
            XmlElement entityElement;
            foreach (WordNode node in wordNodes)
            {
                wordElement = document.CreateElement("wordGroup");
                wordRootElement.AppendChild(wordElement);

                // Save settings.
                settingRootElement = document.CreateElement("settings");
                wordElement.AppendChild(settingRootElement);

                settingRootElement.AppendChild(CreateSetting(document, "BackColor", ColorTranslator.ToHtml(node.BackColor)));
                settingRootElement.AppendChild(CreateSetting(document, "ForeColor", ColorTranslator.ToHtml(node.ForeColor)));
                settingRootElement.AppendChild(CreateSetting(document, "Font", converter.ConvertToString(node.Font)));
                settingRootElement.AppendChild(CreateSetting(document, "NavigateUrl", node.NavigateUrl));
                settingRootElement.AppendChild(CreateSetting(document, "IgnoreCase", node.IgnoreCase.ToString()));

                // Save entities.
                entityRootElement = document.CreateElement("entities");
                wordElement.AppendChild(entityRootElement);
                foreach (string entity in node.Entities)
                {
                    entityElement = document.CreateElement("entity");
                    entityElement.InnerText = entity;
                    entityRootElement.AppendChild(entityElement);
                }
            }

            return element;
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="document">The document which will contain the setting.</param>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private static XmlElement CreateSetting(XmlDocument document, string name, string value)
        {
            XmlElement element = document.CreateElement("setting");
            element.SetAttribute("name", name);
            element.InnerText = value;
            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="IScanner"/> implementation will generally have a reference to a 
        /// <see cref="NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!Enabled)
            {
                if (Child != null)
                {
                    Child.Scan(token);
                }
            }
            else
            {
                bool isMatch = false;

                if (lastToken == null || (lastToken != token && IsValidChar(lastToken[0])))
                {
                    string currentWord = String.Empty;
                    for (int i = Tokenizer.Position; i < Tokenizer.Source.Length; i++)
                    {
                        if (!IsValidChar(Tokenizer.Source[i]))
                            currentWord += Tokenizer.Source[i];
                        else
                            break;
                    }

                    if (currentWord.Length > 0)
                    {
                        for (int i = 0; i < wordNodes.Length; i++)
                        {
                            for (int j = 0; j < wordNodes[i].Entities.Count; j++)
                            {
                                if (wordNodes[i].Entities[j].Length == currentWord.Length)
                                {
                                    if (String.Compare(currentWord, wordNodes[i].Entities[j], wordNodes[i].IgnoreCase) == 0)
                                    {
                                        isMatch = true;
                                        ScannerResult.Add(new Occurrence(Tokenizer.Position, wordNodes[i].Entities[j].Length, wordNodes[i]));
                                        Tokenizer.MoveTo(Tokenizer.Position + wordNodes[i].Entities[j].Length - 1);
                                        break;
                                    }
                                }
                            }
                            if (isMatch)
                                break;
                        }
                    }
                }

                if (!isMatch)
                {
                    if (Child != null)
                    {
                        Child.Scan(token);
                    }
                }
            }

            lastToken = token;
        }

        /// <summary>
        /// Checks whether the valid is a valid char in the sense that it can be placed in front or after a word.
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private static bool IsValidChar(char c)
        {
            return (!char.IsLetter(c)) && (c != '_');
        }

        /// <summary>
        /// Resets the scanner.
        /// </summary>
        public override void Reset()
        {
            lastToken = null;
        }
    }
}